﻿/*******************************************************************
Copyright (c) 2011 이재범

*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
* 
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
* 
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************/

#include "crawler.h"
#include "common.h"
#include <algorithm>

Dictionary<string> Crawler::keyword_left("D:\\equation_search\\keywords\\keyword_left.txt");
Dictionary<string> Crawler::keyword_right("D:\\equation_search\\keywords\\keyword_right.txt");
Hash_set Crawler::url_finished_list("D:\\equation_search\\search\\web\\url_finish");

boost::mutex fin_process;

enum {UNKNOWN = -1, WIKIPEDIA, MATHLINKS};
int Crawler::total_crawler_number = 0;
int Crawler::total_created_crawler_number = -1;

Crawler::Crawler(basic_string<TCHAR> _url_to_process) : url_to_process(_url_to_process)
{
	total_crawler_number ++;
	crawler_id = total_created_crawler_number;
	total_created_crawler_number ++;

	webpage_type = find_webpage_type(url_to_process);
}

void Crawler::operator()()
{
	wcout << url_to_process << endl;
	download_data = new InternetData(url_to_process, "D:\\equation_search\\database\\web\\dat_" + convert_to_string(crawler_id));
	download_data->load_data();
	url_extraction(); // url_process_list 에 다운로드한 데이터에서 url 을 추출한다. 
	equation_extraction(); // 다운로드한 데이터에서 수식을 추출해서 hash 데이터 생성

	// 처리할 url 들의 목록이 있을 때
	if(!url_process_list.empty())
	{
		boost::mutex::scoped_lock scoped_lock(fin_process);

		cout << "Crawler : " << crawler_id << endl;
		wcout << url_to_process << endl;

		int i = 0;
		while ( i < url_process_list.size())
		{
			// url 상의 특수 기호들 처리
			adjust_url(url_process_list[i]);
			// 상대 주소를 수정한다.
			adjust_relative_address(url_process_list[i]);
			// 허용되지 않는 범위의 url 이면
			if(url_restriction(url_process_list[i]))
			{
				url_process_list.erase(i);
				continue;
			}
			i ++;
		}

		// 중복 되는 것을 제외한다. 
		for(int i = 0; i < url_process_list.size(); i++)
		{
			if(url_finished_list.find(wstring_to_string_conv(url_process_list[i])))
			{
				url_process_list.erase(i);
				i --;
			}
			else
				url_finished_list.push_back(wstring_to_string_conv(url_process_list[i]));
		}

		// 자신의 url 이 처리되었음을 알린다. 
		wofstream out("D:\\equation_search\\search\\web\\url_finish", ios::app);
		out << url_to_process << endl;

	}
	// 각 crawler 의 쓰레드를 생성한다. 
	for(int i = 0; i < url_process_list.size(); i ++)
	{
		crawlers.push_back(new boost::thread(Crawler(url_process_list[i])));
		crawlers[i]->join();
	}

}
Crawler::~Crawler()
{
	total_crawler_number --;
}
void Crawler::url_extraction()
{
	vector<string> s_url_process_list;

	string left = "<a href=\"";
	string right = "\"";

	// download 한 데이터 파일에서 특정 url 을 추출한다. 
	extract_string_btw<string>(download_data->get_data(), left, right, s_url_process_list);
	vector<string>::iterator s_itr = s_url_process_list.begin();

	while (s_itr != s_url_process_list.end())
	{
		url_process_list.push_back(string_to_wstring_conv(*s_itr));
		s_itr ++;
	}
}
void Crawler::adjust_relative_address(basic_string<TCHAR>& url)
{
	string::size_type loc = url.find(L"http://");

	// http 있으면 수정할 필요 없다
	if(loc != url.npos)
		return;

	// http://abc/index.php 같은 것을 http://abc/ 로 변환
	// 물론 http://abc/ 는 http://abc/ 로 남는다. 
	basic_string<TCHAR> temp = url_to_process;

	loc = temp.find_last_of(L"/") ;
	if(loc > 1 && temp[loc - 1] == '/')
	{
		// 만일 http://abc 라고 했을 경우 위와 같은 상황이 벌어진다.
		// 이 경우 http://abc/ 로 만들어 주어야 한다.
		temp.push_back('/');
	}
	else
	{
		temp.erase(loc + 1, temp.size() - loc - 1);
	}

	basic_string<TCHAR> current_url = temp; // 현재 url 디렉토리
	basic_string<TCHAR> start_from = temp; // 페이지의 시작점

	// url 을 탐색하게 된다.
	for(int navigator = 0; navigator < static_cast<int>(url.size()); navigator ++)
	{
		if(url[navigator] == '/')
		{
			current_url = start_from.substr(
				0, // 처음 부터
				start_from.find(L"/", 7) + 1 // 첫 번째 / 까지
				);
		}
		// ../ 일 경우 http://abc/xyz/ 와 같은 상태에서
		// http://abc/ 가 되어야만 한다. 
		else if(url[navigator] == '.' && url[navigator + 1] == '.' && url[navigator + 2] == '/')
		{
			current_url.erase(current_url.size() - 1); // 마지막에 붙은 / 를 지운다. 

			// 그리고 상위 디렉토리로 올라간다.
			current_url.erase(current_url.find_last_of(L"/") + 1, url_to_process.size() - url_to_process.find_last_of(L"/") - 1);

			navigator += 2;
		}
		// 현재 디렉토리를 의미하는 것이기 때문에 무시해도 된다. 
		else if(url[navigator] == '.' && url[navigator + 1] == '/')
		{
			navigator ++;
		}
		else // 그냥 문자나 숫자가 나오면 뒤에 가져다 붙이면 된다. 
		{
			current_url.append(url.substr(navigator, url.size() - navigator + 1));
			break;
		}
	}

	url = current_url;
}
void Crawler::adjust_url(basic_string<TCHAR>& url)
{
	string::size_type loc = url.find(L"&amp;");
	while (loc != url.npos)
	{
		url.replace(loc, 5, L"&");
		loc = url.find(L"&amp;");
	}
}
int Crawler::find_webpage_type(basic_string<TCHAR>& url)
{
	basic_string<TCHAR>::size_type loc = url.find(L"wikipedia");
	if(loc != basic_string<TCHAR>::npos)
		return WIKIPEDIA;

	loc = url.find(L"artofproblemsolving");
	if(loc != basic_string<TCHAR>::npos)
		return MATHLINKS;

	return UNKNOWN;
}
void Crawler::equation_extraction()
{
	if(webpage_type == UNKNOWN)
		return;

	vector<string> equation_extracted;
	extract_string_btw(download_data->get_data(), keyword_left[webpage_type], keyword_right[webpage_type], equation_extracted);
	if(webpage_type == MATHLINKS)
	{
		string left = "class=\"latexcenter\" title=\"";
		string right =  "\" alt=\"";
		extract_string_btw(download_data->get_data(), left, right, equation_extracted);
	}

	if(equation_extracted.size() != 0)
	{
		ofstream eq_out("D:\\equation_search\\search\\web\\original\\eq_" + convert_to_filename(url_to_process));
		for(int i = 0; i < static_cast<int>(equation_extracted.size()); i ++)
		{
			eq_out << equation_extracted[i] << endl;
		}
		eq_out.close();
	}

	for(int i = 0; i < static_cast<int>(equation_extracted.size()); i ++)
	{
		hash_data.push_back(new Hash(equation_extracted[i]));
		hash_data[i]->generate_hash_data();
		hash_data[i]->append_hash_data("D:\\equation_search\\search\\web\\" +  convert_to_filename(url_to_process));
		hash_data[i]->append_equation_data("D:\\equation_search\\search\\web\\normal\\" + convert_to_filename(url_to_process));
	}
}
bool Crawler::url_restriction(basic_string<TCHAR>& url)
{
	if(webpage_type != find_webpage_type(url))
		return true;

	basic_string<TCHAR>::size_type loc;
	switch (webpage_type)
	{
	case MATHLINKS:

		// 일단 무조건 Forum 에서만 찾아야 하고
		loc = url.find(L"Forum");
		if(loc == url.npos)
			return true;

		// 테스트
		// 일단 무조건 Forum 에서만 찾아야 하고
		loc = url.find(L"resources");
		if(loc == url.npos)
			return true;

		// Member list 는 찾지 않는다. 
		loc = url.find(L"memberlist");
		if(loc != url.npos)
			return true;

		// Login 화면은 찾지 않는다. 
		loc = url.find(L"ucp");
		if(loc != url.npos)
			return true;

		// blog 는 찾지 않는다. 
		loc = url.find(L"blog");
		if(loc != url.npos)
			return true;

		// # (부분 댓글) 은 찾지 않는다. 
		loc = url.find(L"#");
		if(loc != url.npos)
			return true;

		// code (수식 직접 표기) 은 찾지 않는다. 
		loc = url.find(L"code");
		if(loc != url.npos)
			return true;

		// 이전 페이지
		loc = url.find(L"view=previous");
		if(loc != url.npos)
			return true;

		// 다음 페이지 (중복됨)
		loc = url.find(L"view=next");
		if(loc != url.npos)
			return true;

		loc = url.find(L"posting");
		if(loc != url.npos)
			return true;

		loc = url.find(L"file");
		if(loc != url.npos)
			return true;

		loc = url.find(L"apsjour");
		if(loc != url.npos)
			return true;

		break;

	case WIKIPEDIA:
		break;
	}

	return false;
}
void Crawler::restart()
{
	wifstream in("D:\\equation_search\\search\\web\\url_finish", ios::app);
	wstring url;

	vector<wstring> url_restarted;
	while(in.good())
	{
		getline(in, url);
		url_finished_list.push_back(wstring_to_string_conv(url));
		url_restarted.push_back(url);
	}

	for(int i = 0; i < url_restarted.size(); i ++)
	{
		// 현재 처리할 url
		url_to_process = url_restarted[i];
		
		// 생성자에서 해주는 것은 다 해줘야 한다.
		total_crawler_number ++;
		crawler_id = total_created_crawler_number;
		total_created_crawler_number ++;

		webpage_type = find_webpage_type(url_to_process);

		download_data = new InternetData(url_to_process, "D:\\equation_search\\database\\web\\dat_" + convert_to_string(crawler_id));
		download_data->load_data();
		url_extraction(); // url_process_list 에 다운로드한 데이터에서 url 을 추출한다. 

		// operator()() 와 거의 같지만 equation_extraction 을 할 필요가 없다!

		// 처리할 url 들의 목록이 있을 때
		if(!url_process_list.empty())
		{
			boost::mutex::scoped_lock scoped_lock(fin_process);

			cout << "Crawler : " << crawler_id << endl;
			wcout << url_to_process << endl;

			int i = 0;
			while ( i < url_process_list.size())
			{
				// url 상의 특수 기호들 처리
				adjust_url(url_process_list[i]);
				// 상대 주소를 수정한다.
				adjust_relative_address(url_process_list[i]);
				// 허용되지 않는 범위의 url 이면
				if(url_restriction(url_process_list[i]))
				{
					url_process_list.erase(i);
					continue;
				}
				i ++;
			}

			// 중복 되는 것을 제외한다. 
			for(int i = 0; i < url_process_list.size(); i++)
			{
				if(url_finished_list.find(wstring_to_string_conv(url_process_list[i])))
				{
					url_process_list.erase(i);
					i --;
				}
				else
					url_finished_list.push_back(wstring_to_string_conv(url_process_list[i]));
			}

			// 자신의 url 이 처리되었음을 알린다. 
			wofstream out("D:\\equation_search\\search\\web\\url_finish", ios::app);
			out << url_to_process;

		}
		// 각 crawler 의 쓰레드를 생성한다. 
		for(int i = 0; i < url_process_list.size(); i ++)
		{
			crawlers.push_back(new boost::thread(Crawler(url_process_list[i])));
			crawlers[i]->join();
		}
	}
}